 package encryptionEngines;

import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;

import utils.ByteTools;

public class AES {

	private ByteTools utils;
	private PaddedBufferedBlockCipher cipher = null;
	private int blockSize = 16;
	private KeyParameter kp;
	private byte[] key;
	
	/**
	 * Crea una nueva instacia de un motor de cifrado de algoritmo AES-128
	 * y genera una clave aleatoria para ser usada en las operaciones.
	 */
	public AES() {
		utils = new ByteTools();
		generateKey();
		cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(
				new AESEngine()));
	}
	
	/**
	 * Regresa en un arreglo de 16 bytes la clave actual del motor AES-128.
	 * @return Clave del motor AES
	 */
	public byte[] getKey() {
		return key;
	}

	/**
	 * Actualiza la clave del motor AES para las operaciones.
	 * @param key Clave AES de 16 bytes
	 */
	public void setKey(byte[] key) {
		this.key = key;
		kp = new KeyParameter(key);
	}
	/**
	 * Cifra los bytes con la clave AES-128 actual del motor, regresa los datos cifrados
	 * por el algoritmo.
	 * @param data Arreglo de bytes a cifrar
	 * @return Arreglo de bytes cifrados por el  motor
	 * @throws Exception Error con la clave o los bytes son corruptos.
	 */
	public byte[] encrypt(byte[] data) throws Exception {
		cipher.init(true, kp);
		return processBytesBlock(data);
	}
	
	/**
	 * Decifra los datos de un bloque AES-128 con la clave actual de motor.
	 * @param data Arreglo de bytes que se van a decifrar
	 * @return Arreglo de bytes decifrados por el motor
	 * @throws Exception Error con la clave del motor.
	 */
	public byte[] decrypt(byte[] data) throws Exception {
		cipher.init(false, kp);
		return processBytesBlock(data);
	}

	/**
	 * Actualiza la clave actual del motor AES
	 */
	public void generateKey() {
		key = utils.getRandomBytes(blockSize);
		kp = new KeyParameter(key);
	}

	private byte[] processBytesBlock(byte[] data) throws Exception {
		int in = cipher.getBlockSize();
		int out = cipher.getOutputSize(in);
		int b = utils.getBlocksOf(data.length, in);
		byte[] result = new byte[b * out];
		int j = 0;

		j = cipher.processBytes(data, 0, data.length, result, 0);
		j += cipher.doFinal(result, j);
		
		if (j != result.length)
			return utils.adjust(result, j);
		return result;
	}
}
